/******************************************
Shaoni Sinha (shaoni@uw.edu)
Description: Server side code
******************************************/

import java.net.*;
import java.sql.Timestamp;
import java.util.Date;
import java.util.HashMap;
import java.io.*;

public class Server {
	
	private int portNumber;
	public HashMap<String, String> kvStore; /* The key-value store */
	private File serverLog;
	private ServerSocket serverSocket;
	private static boolean isUDP; 
	
	public static void main(String[] args) {
		int port = 0;
		boolean hasPort = false;
		isUDP = false;
		
		/* Check the sanity of arguments */
		if(args.length < 2)
		{
			PrintUsage();
			return;
		}
		/* Parse arguments */
		for(int i = 0; i < args.length; i++)
		{
			if(args[i].equalsIgnoreCase("-p") && (i < args.length-1))
			{
				port = Integer.parseInt(args[i+1]);
				hasPort = true;
			}
			if(args[i].equalsIgnoreCase("-t") && (i < args.length-1))
			{
				if(args[i+1].equalsIgnoreCase("UDP")){
					isUDP = true;
				} 
				else if(args[i+1].equalsIgnoreCase("TCP")){
					isUDP = false;
				} 
				else {
					PrintUsage();
					return;
				}
			}
		}
		if(!hasPort)
		{
			PrintUsage();
			return;
		}
	    Server server = new Server(port);
	    System.out.println("Server started ....");
	    if(!isUDP){
	    	server.processRequestWithTCP();
	    } else {
	    	server.processRequestWithUDP();
	    }
	  }
	
	/* Command-line usage */
	private static void PrintUsage()
	{
		System.out.println("Usage: java Server -p <port-number> [-t (TCP|UDP)]");
		System.out.println("          -t communication protocol (default: TCP)");
	}

	public Server(int port){
		portNumber = port;
		kvStore = new HashMap<String, String>();
		serverLog = new File("serverlog.txt");
	}
	
	/* UDP Communication */
	private void processRequestWithUDP()
	{
		while(true) {
			DatagramSocket aSocket = null;
			try{
				 aSocket = new DatagramSocket(portNumber);
				 byte[] buffer = new byte[1024];
				 DatagramPacket request = new DatagramPacket(buffer, buffer.length);
				 aSocket.receive(request);
				 String clientRequest = new String(request.getData());
				 clientRequest = clientRequest.trim();
				 String lineArray[] = clientRequest.split(" ");
				 String serverResponse = this.getResponse(lineArray);
				 if(serverResponse != null){
					 DatagramPacket reply = new DatagramPacket(serverResponse.getBytes(), serverResponse.length(), request.getAddress(), request.getPort());
					 String logString = ("" + GetTimestamp() + " ... request:'" + clientRequest + "', response:'" + serverResponse + "'.\r\n"); 
					 UpdateServerLog(logString);
					 aSocket.send(reply);
				 }
				 else {
					 String errString = ("" + GetTimestamp() + " ... Invalid request from InetAddress-" 
							 							+ request.getAddress() + " and port " 
							 							+ portNumber + ".\r\n");
					 UpdateServerLog(errString);
				 }
			}
			catch (Exception e) {
				System.out.println("Failure in communication!");
				e.printStackTrace();
			}
			finally {
			 if (aSocket != null) 
			   aSocket.close();
			}
		}
	}
	
	/* TCP/IP Communication */
	private void processRequestWithTCP()
	{
		while(true)
		{
			DataInputStream is = null;
			Socket client = null;
		    DataOutputStream os = null;
		    
			try {
				 serverSocket = new ServerSocket(portNumber);
				 String serverResponse = null;
				 client = serverSocket.accept(); // Wait and accept a connection
				 os = new DataOutputStream (client.getOutputStream());
				 is = new DataInputStream(client.getInputStream());
				 String clientRequest = is.readUTF();
				 String lineArray[] = clientRequest.split(" ");
				 serverResponse = this.getResponse(lineArray);
				 if(serverResponse != null){
					 String logString = ("" + GetTimestamp() + " ... request:'" + clientRequest + "', response:'" + serverResponse + "'.\r\n"); 
					 UpdateServerLog(logString);
					 os.writeUTF(serverResponse);
				 }
				 else {
					 String errString = ("" + GetTimestamp() + " ... Invalid request from InetAddress-" 
							 							+ client.getInetAddress() + " and port " 
							 							+ portNumber + ".\r\n");
					 UpdateServerLog(errString);
				 }
			} catch (Exception e) {
				if(e.getMessage() != null) {
					System.out.println("Exception: " + e.getMessage() + "!");
				} else {
					System.out.println("Exception: Failure in communication!");
				}
				e.printStackTrace();
			} finally {
				 /* Cleanup */
				 try {
					serverSocket.close();
					os.close();
					is.close();
					client.close();
				 } catch (IOException e) {
					System.out.println("Failure in closing connection!");
					e.printStackTrace();
				 }
			}
		}
	}
	
	/* Process the request and prepare a server response. */
	private String getResponse(String[] args){
		int len = args.length;
		String response = null;
		if(len!= 0){
			 if(args[0].equalsIgnoreCase("PUT") && len == 3){
				this.kvStore.put(args[1], args[2]);
				response = "Key-Value pair added to KV Store!";
			 } else if(args[0].equalsIgnoreCase("GET") && len == 2){
				 response = this.kvStore.get(args[1]);
			 } else if(args[0].equalsIgnoreCase("DELETE") && len == 2){
				 this.kvStore.remove(args[1]);
				 response = "Key-Value pair corresponding to key-" + args[0] + " deleted!"; 
			 }
	    }
		return response;
    }
	
	/* Create a timestamp. */ 
	private Timestamp GetTimestamp() {
		Date date= new java.util.Date();
		return (new Timestamp(date.getTime()));
	}
	
	/* Update client log. */
	private void UpdateServerLog(String errString) {
		try {
			FileWriter fw = new FileWriter(serverLog, true);
			System.out.print(errString);
			fw.write(errString);
			fw.close();
		} catch (IOException e) {
			System.out.println(GetTimestamp() + " ... Failure to update clientlog!");
			e.printStackTrace();
		}
		return;
	}
	
}
